﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace Mesuro.Equation {
	public sealed class EqScope {
		//-- Static Definition --//
		private static EqScope _globalScope = new EqScope( null );
		public static EqScope Intrinsic {
			get { return EqScope._globalScope; }
			set { EqScope._globalScope = value; }
		}

		private static EqScope _defaultScope = null;
		public static EqScope Default {
			get {
				if (_defaultScope == null)
					_defaultScope = new EqScope( null );
				return _defaultScope;
			}
		}

		static EqScope() {
			System.Runtime.CompilerServices.RuntimeHelpers.RunClassConstructor( typeof( EqRealConstant ).TypeHandle );
		}

		//-- Class Definition --//
		private Dictionary<string, EqSymbol> SymbolMap = new Dictionary<string, EqSymbol>();
		private readonly EqScope _parentScope;
		public EqScope ParentScope {
			get { return _parentScope; }
		}

		public EqSymbol this[string symbol] {
			get {
				return SymbolMap[symbol];
			}
		}
		public EqSymbol this[EqSymbolInstance symbol] {
			get {
				return FindSymbol( symbol );
			}
		}

		//Private cctor
		private EqScope( EqScope parent ) {
			_parentScope = parent;
		}

		//Public equivalent cctor
		public EqScope DeriveScope() {
			return new EqScope( this );
		}

		public EqSymbol CreateSymbol( String key, EqSymbolFlags flags = EqSymbolFlags.None, EqTerm arguments = null ) {
			return CreateSymbol( key, (EqTerm)null,
				flags, arguments );
		}
		public EqSymbol CreateSymbol( String key, EqTerm value, EqSymbolFlags flags = EqSymbolFlags.None, EqTerm arguments = null ) {
			EqSymbol sym;

			if (!SymbolMap.TryGetValue( key, out sym ))
				sym = new EqSymbol( key );

			EqListTerm argList = (arguments == null) ? new EqListTerm() : EqListTerm.Cast( arguments.Evaluate( Intrinsic ) );

			if (sym.SelectDefinition( argList.Count ))
				throw new InvalidOperationException( String.Format( "Cannot create symbol '{0}' with {1} args. Symbol already exists.", key, argList.Count ) );

			sym.AddDefinition( new EqSymbolDefinition( argList, flags ) );

			sym.Value = value;

			SymbolMap.Add( key, sym );

			return sym;
		}
		public EqSymbol CreateSymbol( String key, EqSymbolExternalEvaluator evaluator, EqSymbolFlags flags = EqSymbolFlags.None, EqTerm arguments = null ) {
			EqSymbol sym;

			if (!SymbolMap.TryGetValue( key, out sym ))
				sym = new EqSymbol( key );

			EqListTerm argList = (arguments == null) ? new EqListTerm() : EqListTerm.Cast( arguments.Evaluate( Intrinsic ) );

			if (sym.SelectDefinition( argList.Count ))
				throw new InvalidOperationException( String.Format( "Cannot create symbol '{0}' with {1} args. Symbol already exists.", key, argList.Count ) );

			sym.AddDefinition( new EqExternalSymbolDefinition( argList, flags, evaluator ) );

			SymbolMap.Add( key, sym );

			return sym;
		}

		public EqSymbol FindSymbol( string key ) {
			EqSymbol ret;
			if (!SymbolMap.TryGetValue( key, out ret ))
				if (this == Intrinsic || (ret = Intrinsic.FindSymbol( key )) == null)
					ret = (ParentScope == null) ? null : ParentScope.FindSymbol( key );
			return ret;
		}
		public EqSymbol FindSymbol( EqSymbolInstance symbol ) {
			return FindSymbol( symbol.Key );
		}

		public EqSymbol FindOrCreateSymbol( String key ) {
			EqSymbol sym;
			if ((sym = FindSymbol( key )) == null)
				return CreateSymbol( key );
			return sym;
		}

		internal void SetSymbol( EqSymbolInstance instance, EqTerm value ) {
			EqSymbol sym = FindSymbol( instance );

			if (sym == null) {
				//No corresponding symbol exists, create one and return
				sym = CreateSymbol( instance.Key, value, EqSymbolFlags.None, instance.Argument );
				return;
			}

			//A corresponding symbol exists

			EqListTerm args = (instance.Argument == null) ? new EqListTerm() : EqListTerm.Cast( instance.Argument.Evaluate( Intrinsic ) );

			if (!sym.SelectDefinition( args.Count ))
				sym.AddDefinition( new EqSymbolDefinition( args, EqSymbolFlags.None ) );

			sym.Value = value;
		}

		public void Reset() {
			SymbolMap.Clear();
		}

		public bool IsParentOf( EqScope scope ) {
			while (scope != null && scope != this)
				scope = scope._parentScope;
			return scope != null;
		}
	}
}
